package com.licode.codekit.buildprj;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.util.ResourceUtil;
import com.licode.codekit.db.EntityBuilder;
import com.licode.codekit.db.GenerateCtx;
import com.licode.codekit.db.Table;
import com.licode.codekit.utils.FileUtil;
import com.licode.codekit.utils.StrUtil;
import org.jsoup.internal.StringUtil;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author licode
 */
public class CodeGenerateBuilder {
    final String MAPPER_HEAD = "org.mapstruct.Mapper;";
    final String MAPPER_ENTITY = MAPPER_HEAD + "\n\nimport {entity-package}.{entity-name}{entity-suffix};\n"
            + "import {po-package}.{entity-name}PO;";

    final String UTIL_LIST = "import java.util.List;\n" +
            "import org.mapstruct.Mapper;";

    final String ASSEMBLER_HEAD = "Assembler {";
    final String ASSEMBLER_FROM = "Assembler {\n\n    {entity-name}PO from{entity-name}({entity-name}{entity-suffix} source);\n"
            + "    {entity-name}{entity-suffix} to{entity-name}({entity-name}PO source);\n"
            + "    List<{entity-name}PO> from{entity-name}(List<{entity-name}{entity-suffix}> source);\n"
            + "    List<{entity-name}{entity-suffix}> to{entity-name}(List<{entity-name}PO> source);";

    final String ASSEMBLER_CONTENT = "package {assembler-package};\n" +
            "\n" +
            "import java.util.List;\n" +
            "import org.mapstruct.Mapper;\n" +
            "\n" +
            "/**\n" +
            " * {description}\n" +
            " *\n" +
            " * @author {author}\n" +
            " * @date {date}\n" +
            " */\n" +
            "@Mapper(componentModel = \"spring\")\n" +
            "public interface {module-class-name}Assembler {\n" +
            "}\n";

    private String getRootDir(String dir) {
        String zipRoot = dir;
        if (dir != null && dir.length() >= 2) {
            if (!dir.endsWith("/")) {
                zipRoot = dir + "/";
            }

            return zipRoot;
        } else {
            return "";
        }
    }

    private String getCodeTemplateFile(ProjectContext prjCtx, String tempType) {
        return CodeKitConf.BM_DEFAULT.equals(tempType) ? "normal-code.zip" : "six-arch-code.zip";
    }

    public boolean buildEntityCode(Project rootPrj, ProjectContext prjCtx, String tempType) {
        int tableIndex = 0;
        boolean buildFlag = true;
        String rootDir = this.getRootDir(prjCtx.getRootDir()) + "/";
        File file = new File(rootDir);
        String tempFile = getCodeTemplateFile(prjCtx, tempType);

        if (!(file.exists() && file.isDirectory())) {
            Messages.showMessageDialog(rootPrj, "项目不存在！",
                    "温馨提示", Messages.getInformationIcon());
            return false;
        }

        prjCtx.setHasClient(false);
        prjCtx.setCurrentDir(rootDir);

        if (!preHandleCodeTemplate(prjCtx, tempFile)) {
            Messages.showMessageDialog(rootPrj, "当前模块不能自动生成代码！",
                    "温馨提示", Messages.getInformationIcon());
            return false;
        }

        for (Table table : prjCtx.getTables()) {
            try (InputStream is = FileUtil.getResourceAsStream(tempFile);
                 ZipInputStream isZip = new ZipInputStream(is, StandardCharsets.UTF_8)) {
                generateCodeByTables(prjCtx, isZip, table,
                        tableIndex == 0 && prjCtx.isBuildDirWhenGenCode());
            } catch (Exception e) {
                buildFlag = false;
                break;
            }
            ++tableIndex;
        }

        if (!buildFlag) {
            Messages.showMessageDialog(rootPrj, "生成代码失败！",
                    "温馨提示", Messages.getInformationIcon());
        }

        return buildFlag;
    }

    private boolean preHandleCodeTemplate(ProjectContext prjCtx, String tempFile) {
        boolean result = false;

        try (InputStream is = FileUtil.getResourceAsStream(tempFile);
             ZipInputStream inputStream = new ZipInputStream(is, StandardCharsets.UTF_8)) {
            int pos;
            String name;
            String fileName;
            String midPackage;
            String pkgName;
            ZipEntry nextEntry = inputStream.getNextEntry();

            while (nextEntry != null) {
                name = nextEntry.getName();
                pos = name.lastIndexOf("/");

                fileName = name.substring(pos + 1);
                midPackage = name.substring(0, pos + 1);
                pkgName = prjCtx.findValidPackage(midPackage);
                if (StringUtil.isBlank(pkgName)) {
                    throw new RuntimeException("不可用的模块");
                }

                if ("TagPO.java".equals(fileName)) {
                    prjCtx.setPoPackage(pkgName + "." + midPackage.replace("/", "."));
                } else if ("TagDTO.java".equals(fileName) || "TagEntity.java".equals(fileName)) {
                    prjCtx.setEntitySuffix(StrUtil.getFileName(fileName.substring(3)));
                    prjCtx.setEntityPackage(pkgName + "." + midPackage.replace("/", "."));
                } else if ("EntityNameDao.java".equals(fileName)) {
                    prjCtx.setDaoPackage(pkgName + "." + midPackage.replace("/", "."));
                } else if ("EntityNameMapper.java".equals(fileName)) {
                    prjCtx.setMapperPackage(pkgName + "." + midPackage.replace("/", "."));
                } else if ("TagAssembler.java".equals(fileName)) {
                    prjCtx.setAssemblerPackage(pkgName + "." + midPackage.replace("/", "."));
                }

                inputStream.closeEntry();
                nextEntry = inputStream.getNextEntry();
            }
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    private void generateCodeByTables(ProjectContext prjCtx,
                                      ZipInputStream inputStream,
                                      Table table, boolean needBuildDir) throws IOException {
        int pos;
        String name;
        String midDir;
        String fileName;
        String pathName = "";
        String javaRoot = "";
        ZipEntry nextEntry = inputStream.getNextEntry();

        while (nextEntry != null) {
            name = nextEntry.getName();
            pos = name.lastIndexOf("/");

            fileName = name.substring(pos + 1);
            midDir = name.substring(0, pos + 1);
            javaRoot = prjCtx.findValidSrcRoot(midDir);
            boolean canContinue = true;
            boolean isEntity = "TagPO.java".equals(fileName) ||
                    "TagDTO.java".equals(fileName) ||
                    "TagEntity.java".equals(fileName);

            if (prjCtx.isOnlyEntity()) {
                canContinue = isEntity;
            }

            if (StringUtil.isBlank(javaRoot) || !canContinue) {
                inputStream.closeEntry();
                nextEntry = inputStream.getNextEntry();
                continue;
            }

            pathName = javaRoot + midDir;

            if (!nextEntry.isDirectory()) {
                String result = FileUtil.readText(inputStream, "utf-8");
                StrUtil.calcFilePackage(pathName, prjCtx);

                if (isEntity) {
                    FileUtil.deleteFile(pathName);
                }
                FileUtil.makeDirs(pathName);
                handleFileContent(prjCtx, table, pathName, fileName, result);
            } else {
                if (needBuildDir) {
                    FileUtil.makeDirs(pathName);
                }
            }

            inputStream.closeEntry();
            nextEntry = inputStream.getNextEntry();
        }
    }

    private String findAndCreateAssembler(String fileDir, String moduleClassName) {
        String assemblerFile;
        File file = new File(fileDir);
        if (!file.exists()) {
            file.mkdir();
        }
        File[] files = file.listFiles();
        if (null != files && files.length > 0) {
            assemblerFile = files[0].getAbsolutePath();
        } else {
            assemblerFile = fileDir + moduleClassName + "Assembler.java";
            try {
                File assFile = new File(assemblerFile);
                assFile.createNewFile();
                FileUtil.saveStrToFile(assFile, ASSEMBLER_CONTENT.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return assemblerFile;
    }

    private void handleFileContent(ProjectContext prjCtx,
                                   Table table,
                                   String fileDir,
                                   String fileName,
                                   String result) {
        String filePath;
        GenerateCtx gCtx;
        EntityBuilder builder = new EntityBuilder();
        String moduleClassName = prjCtx.getModuleClassName();
        String entityName = prjCtx.getEntityName(table);

        if ("TagPO.java".equals(fileName) ||
                "TagDTO.java".equals(fileName) ||
                "TagEntity.java".equals(fileName)) {
            gCtx = new GenerateCtx(fileDir, StrUtil.getFileName(fileName.substring(3)));
            gCtx.setAuthor(prjCtx.getAuthor());
            gCtx.setTablePrefix(prjCtx.getTablePrefix());
            gCtx.setJdkVersion(prjCtx.getJdkVersion());
            gCtx.setSpringBoot2x(prjCtx.isSpringBoot2x());

            builder.generate(table, gCtx);
        } else if ("TagAssembler.java".equals(fileName)) {
            // 生成对象转换
            String tmPath = findAndCreateAssembler(fileDir, moduleClassName);
            String content = FileUtil.readFileContent(tmPath);
            String assemblerClassName = StrUtil.getFileName(tmPath);

            prjCtx.setAssemblerClassName(assemblerClassName);

            if (null != content && !content.isEmpty()) {
                if (!content.contains("from" + entityName + prjCtx.getEntitySuffix())) {
                    content = content.replace(MAPPER_HEAD, MAPPER_ENTITY
                            .replace("{group-id}", prjCtx.getGroupId())
                            .replace("{package}", prjCtx.getGroupId())
                            .replace("{entity-package}", prjCtx.getEntityPackage())
                            .replace("{entity-suffix}", prjCtx.getEntitySuffix())
                            .replace("{name}", prjCtx.getModuleName())
                            .replace("{entity-name}", entityName));
                    content = prjCtx.handleContent(content, 0)
                            .replace(ASSEMBLER_HEAD, ASSEMBLER_FROM
                                    .replace("{entity-name}", entityName)
                                    .replace("{entity-suffix}", prjCtx.getEntitySuffix()));
                    saveStrToFile(tmPath, content.getBytes(StandardCharsets.UTF_8));
                }
            }
        } else {
            filePath = fileDir + handleFile(fileName, entityName);
            File saveFile = new File(filePath);
            if (!saveFile.exists()) {
                saveStrToFile(saveFile, this.handleEntityFile(prjCtx, result, table));
            }
        }
    }

    private String handleFile(String filename, String entityName) {
        if (filename.startsWith("EntityName")) {
            return entityName + filename.substring("EntityName".length());
        } else {
            return filename;
        }
    }

    private void saveStrToFile(String filepath, byte[] content) {
        File file = new File(filepath);
        saveStrToFile(file, content);
    }

    private void saveStrToFile(File filepath, byte[] content) {
        FileOutputStream fileOutputStream;
        BufferedOutputStream bufferedOutputStream;

        try {
            fileOutputStream = new FileOutputStream(filepath);
            bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            bufferedOutputStream.write(content);
            bufferedOutputStream.close();
            fileOutputStream.close();
        } catch (Exception ignore) {
            ignore.printStackTrace();
        }
    }

    private byte[] handleEntityFile(ProjectContext prjCtx, String result, Table table) {
        String entityName = prjCtx.getEntityName(table);
        String entityField = StrUtil.firstLower(entityName);
        return prjCtx.handleContent(result)
                .replace("{entity-c}", StrUtil.getUrlPath(entityField))
                .replace("{entity-name}", entityName)
                .replace("{entity-field}", entityField)
                .getBytes(StandardCharsets.UTF_8);
    }
}
